<?php
/**
 * Created by paperphp
 * User: 22071
 * Date: 2019/9/20
 * Email: <zhendongdong@foxmail.com>
 */

namespace paper\routing;


use Closure;
use paper\App;
use paper\http\Response;
use paper\View;

class Router
{
    private $routesGroup = [];
    private $routesGroupTract = [];
    private $path;
    private $app;
    private $parameters = [];
    public $controller = 'index';
    public $method = 'index';

    public $_controller = 'index';
    public $_method = 'index';

    private $inGroupFlagCount = 0;
    private $routePosition = -1;
    private $routeNames = [];

    public function __construct(App $app)
    {
        $this->app  = $app;
        $this->path = $app->request->path();
    }

    /**
     * @return array
     */
    public function getParameters(): array
    {
        return $this->parameters;
    }

    public function group($attributes, Closure $routes)
    {
        $this->beginGroup();
        $routes();
        $this->routesGroup[] = [
            'is_group'   => true,
            'attributes' => $attributes,
            'routes'     => $this->routesGroupTract,
        ];
        $this->endGroup();
    }

    public function any($uri, $action)
    {
        return $this->addRoute(['GET', 'POST', 'DELETE', 'OPTIONS', 'PUT'], $uri, $action);
    }

    public function get($uri, $action)
    {
        return $this->addRoute(['GET'], $uri, $action);
    }

    public function post($uri, $action)
    {
        return $this->addRoute(['POST'], $uri, $action);
    }

    public function name($name)
    {
        $this->routeNames[$name] = $this->routePosition;
    }


    private function beginGroup()
    {
        $this->inGroupFlagCount++;
    }

    private function endGroup()
    {
        $this->inGroupFlagCount--;
        $this->routesGroupTract = [];
    }

    private function addRoute($method, $uri, $action)
    {
        $this->routePosition++;
        if ($this->inGroupFlagCount) {
            $this->routesGroupTract[] = ['method' => $method, 'uri' => $this->prepareUri($uri), 'action' => $action];
        } else {
            $this->routesGroup[] = [
                'is_group'   => false,
                'attributes' => [],
                'routes'     => ['method' => $method, 'uri' => $this->prepareUri($uri), 'action' => $action],
            ];
        }
        return $this;
    }

    /**
     * @return Response
     * @throws \ReflectionException
     * @throws \paper\Exception
     */
    public function dispatch()
    {
        //解析控制器
        $route = $this->findRoute() ?: $this->parseUriToDefaultRoute($this->path);
        return $this->enterMiddleware()
            ->then($this->app->request, function () use ($route) {
                return $this->prepareResponse($this->run($route));
            });
    }

    private function parseUriToDefaultRoute($path)
    {
        //解析控制器
        $route   = [];
        $pathArr = explode("/", substr($path, 1), 2);
        if (isset($pathArr[0]) && $pathArr[0]) {
            $this->_controller = $pathArr[0];
            $this->controller  = toCamelCase($this->_controller);
        }
        //解析方法
        if (isset($pathArr[1])) {
            $this->_method = $pathArr[1];
            $this->method  = toCamelCase($this->_method);
        }
        //$route['action'] = [ucfirst($this->controller) . 'Controller', $this->method];
        return $this->buildRoute(ucfirst($this->controller) . 'Controller', $this->method);
    }


    private function enterMiddleware()
    {
        return $this->getMiddleware();
    }

    private function getMiddleware()
    {
        return $this->app->middleware;
    }

    /**
     * 预处理Response响应
     * @param $result
     * @return Response
     */
    public function prepareResponse($result)
    {
        return self::toResponse($result);
    }

    /**
     * 将返回内容转成Response
     * @param $result
     * @return Response
     */
    public static function toResponse($result)
    {
        if (!is_null($result)) {
            if ($result instanceof View) {
                $responseType = 'view';
            } else if (is_array($result)) {
                $responseType = 'json';
            } else {
                $responseType = 'html';;
            }
            return Response::create($result, 200, $responseType);
        }
        $result  = ob_get_clean();
        $content = false === $result ? '' : $result;
        return Response::create($content, 200);
    }

    /**
     * @param $route
     * @return mixed|\paper\http\Response
     * @throws \ReflectionException
     * @throws \paper\Exception
     */
    private function run($route)
    {
        $action = $route['action'];
        if ($action instanceof \Closure) {
            return call_user_func_array($action, $this->getParameters());
        } elseif (is_array($action)) {
            return $this->actionDispatcher(
                $this->prepController($action[0]),
                $action[1]
            );
        } elseif (is_string($action)) {
            if (strrpos($action, '@')) {
                [$controller, $actionMethod] = explode("@", $action, 2);
            } else {
                $controller   = $action;
                $actionMethod = 'index';
            }
            return $this->actionDispatcher(
                $this->prepController($controller),
                $actionMethod
            );
        }
    }

    private function prepController($controller)
    {
        return 'app\\controllers\\' . $controller;
    }

    /**
     * @param $abstract
     * @param $action
     * @return \paper\http\Response
     * @throws \ReflectionException
     * @throws \paper\Exception
     */
    private function actionDispatcher($abstract, $action)
    {
        return $this->app->invokeMethod($abstract, $action);
    }

    /**
     * @throws \ReflectionException
     * @throws \paper\Exception
     */
    private function findRoute()
    {
        foreach ($this->routesGroup as $item) {
            if ($route = $this->matchGroupRoute($item)) {
                return $route;
            }
        }
        return false;
    }

    /**
     * @param $group
     * @return bool|array
     * @throws \ReflectionException
     * @throws \paper\Exception
     */
    public function matchGroupRoute($group)
    {
        $isGroup    = $group['is_group'];
        $attributes = $group['attributes'];
        $routes     = $group['routes'];
        $prefix     = '';
        if (isset($attributes['prefix'])) {
            //检测是否能够匹配到前缀
            $prefix = '/' . $attributes['prefix'];
            if (strrpos($this->path, $prefix) !== 0) {
                return false;
            }
        }
        if ($isGroup === false) {
            if ($this->matchRule($routes, $prefix)) {
                return $routes;
            }
        } else {
            //继续匹配规则
            foreach ($routes as $route) {
                //匹配到规则
                if ($this->matchRule($route, $prefix)) {
                    //如果设置了中间件
                    if (isset($attributes['middleware'])) {
                        $this->addMiddleware($attributes['middleware']);
                    }
                    return $route;
                }
            }
            if ($prefix && isset($attributes['second']) && $attributes['second']) {
                if (isset($attributes['middleware'])) {
                    $this->addMiddleware($attributes['middleware']);
                }
                return $this->getBindControllerAction($prefix, $attributes['second']);
            }
        }

        return false;
    }

    /**
     * 获取分组绑定的子目录
     * @param $prefix
     * @param $second
     * @return array
     */
    private function getBindControllerAction($prefix, $second)
    {
        $path   = $this->path;
        $action = str_replace($prefix . '/', '/', $path);
        $route  = $this->parseUriToDefaultRoute($action);
        return $this->buildRoute($second . '\\' . $route['action'][0], $route['action'][1]);
    }

    private function buildRoute($controller, $method)
    {
        return ['action' => [$controller, $method]];
    }

    private function addMiddleware($middleware)
    {
        if (is_array($middleware)) {
            $this->app->middleware->import($middleware);
        } else {
            $this->app->middleware->add($middleware);
        }
    }

    private function prepareUri($uri)
    {
        if (strrpos($uri, '/') !== 0) {
            $uri = '/' . $uri;
        }
        return $uri;
    }

    private function uriToPattern($uri)
    {
        $path = preg_quote($uri, '/');
        $path = preg_replace("/\\\{([0-9a-zA-Z_]+)\\\}/", "(?P<$1>[^\/]+)", $path);
        return $path;
    }

    /**
     * @param $route
     * @param string $prefix
     * @return mixed
     * @throws \ReflectionException
     * @throws \paper\Exception
     */
    private function matchRule($route, $prefix = '')
    {
        $method        = $route['method'];
        $uri           = $prefix . $route['uri'];
        $requestMethod = strtoupper($this->app->request->method());

        if (!in_array($requestMethod, $method)) {
            return false;
        }

        $pattern = $this->uriToPattern($uri);
        if (!preg_match('/^' . $pattern . '$/is', $this->path, $m)) {
            return false;
        }

        array_shift($m);
        $this->parameters = $m;
        return true;

    }
}